class Controller{
  
 int numLayers;
 int numData;
 ArrayList<Layer> layers;
 private float[] g0;
 int numFilterLayers;
 ArrayList<Layer> filterLayers;
 
 //
 //constructor
 //
 Controller(int _numLayers, int _numData){
  numLayers = _numLayers;
  numData = _numData;
  layers = new ArrayList<Layer>(_numLayers);
  g0 = new float[_numData];
 } 
 
 //
 //add layers
 //
 void addLayer(int id, int _numdata){
   int p = 0;
   if( layers.size() != 0 ) p = layers.size();
   layers.add(p, new Layer(id, _numdata)); 
 }
 
 void addLayer(int id, int _numdata, color _color){
   int p = 0;
   if( layers.size() != 0 ) p = layers.size();

   layers.add(p, new Layer(id, _numdata, _color)); 
 }
 
 void addLayer(int id, int _numdata, color _color, boolean init_integrator){
   int p = 0;
   if( layers.size() != 0 ) p = layers.size();

   layers.add(p, new Layer(id, _numdata, _color, init_integrator)); 
 }
 
 void addLayer(int id, int _numdata, color _color, color _strokeColor){
   int p = 0;
   if( layers.size() != 0 ) p = layers.size();
   layers.add(p, new Layer(id, _numdata, _color, _strokeColor)); 
 }
 
 void initializeUpper(int layerID, int pos, float data ){
    layers.get(layers.size() - 1).initializeUpper(pos, data);
 }
 
 void initializeLower(int layerID, int pos, float data ){
    layers.get(layers.size() - 1).initializeLower(pos, data);
 }
 
 //
 // add data to layers
 //
 
 void setData(int layerID, int pos, float data){
   layers.get(layerID).setData(pos, data);
 }
 
  void setUpperOffset(int layerID, int pos, float offset){
   layers.get(layerID).setUpperOffset(pos, offset);
 }
 
 void setLowerOffset(int layerID, int pos, float offset){
   layers.get(layerID).setLowerOffset(pos, offset);
 }
 
 void setg0(int pos, float _g0){
     g0[pos] = _g0; 
 }
 
 void targetUpperData(int layerID, int pos, float data){
   layers.get(layerID).targetUpperData(pos, data);
 }
 
 void targetLowerData(int layerID, int pos, float data){
   layers.get(layerID).targetLowerData(pos, data);
 }
 
 
 void setColor(int layerID, color _color){
   layers.get(layerID).setColor(_color);
 }
 
 void targetColor(int layerID, color _color){
   layers.get(layerID).targetColor(_color);
 }
 
 
 void setStrokeColor(int layerID, color _color){
//   for (int lID = 0; lID < numLayers; lID++)
//     if(layerID == layers.get(lID).id)   
    layers.get(layerID).setStrokeColor(_color);
 }
 
 void targetStrokeColor(int layerID, color _color){
   layers.get(layerID).targetStrokeColor(_color);
 }
 
 void setHighlightColor(int layerID, color _color){
//   for (int lID = 0; lID < numLayers; lID++)
//     if(layerID == layers.get(lID).id) 

     layers.get(layerID).setHighlightColor(_color);
 }
 
 void targetHighlightColor(int layerID, color _color){
   layers.get(layerID).targetHighlightColor(_color);
 }
 
 //
 // get data
 //
 
 color getColor(int layerID){
   if(layerID >= layers.size()) return 0;
   return layers.get(layerID).getColor();
 }
 
 color getStrokeColor(int layerID){
//   for (int lID = 0; lID < numLayers; lID++)
//     if(layerID == layers.get(lID).id) return layers.get(lID).getStrokeColor();
    if(layerID >= layers.size()) return 0;
   return layers.get(layerID).getStrokeColor();
 }
 
 color getHighlightColor(int layerID){
//   for (int lID = 0; lID < numLayers; lID++)
//     if(layerID == layers.get(lID).id) return layers.get(lID).getHighlightColor();
    if(layerID >= layers.size()) return 0;
   return layers.get(layerID).getHighlightColor();
 }
 
 float getUpperData(int layerID, int pos){
   if(layerID >= layers.size()) return 0;
   return layers.get(layerID).getUpperData(pos);
 }
 
  float getLowerData(int layerID, int pos){
    if(layerID >= layers.size()) return 0;
   return layers.get(layerID).getLowerData(pos);
 }
 
 color getRealColor(int layerID){
   if(layerID >= layers.size()) return 0;
   return layers.get(layerID).getRealColor();
 }
 
 color getRealStrokeColor(int layerID){
   if(layerID >= layers.size()) return 0;
   return layers.get(layerID).getRealStrokeColor();
 }
 
 color getRealHighlightColor(int layerID){
   if(layerID >= layers.size()) return 0;
   return layers.get(layerID).getRealHighlightColor();
 }
 
 float getRealData(int layerID, int pos){
   if(layerID >= layers.size()) return 0;
   return layers.get(layerID).getRealData(pos);
 }
 
 float getUpperOffset(int layerID, int pos){
    if(layerID >= layers.size()) return 0;
   return layers.get(layerID).getUpperOffset(pos);
 }
 
 float getLowerOffset(int layerID, int pos){
   if(layerID >= layers.size()) return 0;
   return layers.get(layerID).getLowerOffset(pos);
 }
 
 float getTotalOffset(int pos){
   return layers.get(0).getUpperOffset(pos);
 }
 
 float getOffset(int layerID, int pos){
   if(layerID >= layers.size()) return 0;
   return layers.get(numLayers - 1).getOffset(pos);
 }
 
 float getg0(int pos){
   return g0[pos];
 }
 

 //
 // update integrators
 //
 void updateColor(int layerId) {
   layers.get(layerId).updateColor();
 }
 
 void updateStrokeColor(int layerId) {
   layers.get(layerId).updateStrokeColor();
 }
 
 void updateHighlightColor(int layerId) {
   layers.get(layerId).updateHighlightColor();
 }
 
  void updateData(int layerId, int pos) {
   layers.get(layerId).updateData(pos);
 }
 
 
 //
 // check integrators
 //
 
 // check functions
 boolean checkUpperDataIntegrator(int layerId, int i, float tol){
  if( layers.get(layerId).upper_integrator[i].value() <= g0[i] + layers.get(layerId).upper_offset[i] + tol && 
      layers.get(layerId).upper_integrator[i].value() >= g0[i] + layers.get(layerId).upper_offset[i] - tol )
    return true;
  return false;
 }
 
 boolean checkLowerDataIntegrator(int layerId, int i, float tol){
  if( layers.get(layerId).lower_integrator[i].value() <= g0[i] + layers.get(layerId).lower_offset[i] + tol && 
      layers.get(layerId).lower_integrator[i].value() >= g0[i] + layers.get(layerId).lower_offset[i] - tol )
    return true;
  return false;
 }
 
 boolean checkStrokeColorIntegrator(int layerId, float tol){
   color c =  layers.get(layerId).getRealStrokeColor();
   color sc = layers.get(layerId).getStrokeColor();
   

  if( red(sc) >= red(c) - tol &&
      green(sc) >= green(c) - tol &&
      blue(sc) >= blue(c) - tol &&
      red(sc) <= red(c) + tol &&
      green(sc) <= green(c) + tol &&
      blue(sc) <= blue(c) + tol
      
    )
    return true;
  return false;
 }
 
 
 boolean checkHighlightColorIntegrator(int layerId, float tol){
   color c =  layers.get(layerId).getRealHighlightColor();
   color sc = layers.get(layerId).getHighlightColor();
   

  if( red(sc) >= red(c) - tol &&
      green(sc) >= green(c) - tol &&
      blue(sc) >= blue(c) - tol &&
      alpha(sc) >= alpha(c) - tol &&
      red(sc) <= red(c) + tol &&
      green(sc) <= green(c) + tol &&
      blue(sc) <= blue(c) + tol &&
      alpha(sc) <= alpha(c) + tol
      
    )
    return true;
  return false;
 }
 
 boolean checkColorIntegrator(int layerId, float tol){
   color c =  layers.get(layerId).getRealColor();
   color sc = layers.get(layerId).getColor();
   
   //println(layerId + " : (" +  red(c) + ", " + blue(c) + ", " + green(c) + ") ");
   
  if( red(sc) >= red(c) - tol &&
      green(sc) >= green(c) - tol &&
      blue(sc) >= blue(c) - tol &&
      alpha(sc) >= alpha(c) - tol &&
      red(sc) <= red(c) + tol &&
      green(sc) <= green(c) + tol &&
      blue(sc) <= blue(c) + tol &&
      alpha(sc) <= alpha(c) + tol
      
    )
    return true;
  return false;
 }
 
 
 //
 // calculations
 //
 
 int matchLayerID(int id){
   //println("id: " +  id);
   //println("numLayers: " +  numLayers);
   for (int lID = 0; lID < numLayers; lID++)
     if(layers.size() > lID) {if(id == layers.get(lID).id) return lID;}
     else break;
   return -1;
 }
 
 int matchLayerIDReverse(int id){
   if(id >= numLayers) return -1;
   return layers.get(id).id;
 }
 
 //
 // sort
 //
 
 
 public class sortByWeight implements Comparator<Layer> {
    @Override
    public int compare(Layer o1, Layer o2) {
       return o1.getWeight().compareTo(o2.getWeight());
    }
  }
  
 public class sortByStandardDev implements Comparator<Layer> {
    @Override
    public int compare(Layer o1, Layer o2) {
       return o1.getStandardDev().compareTo(o2.getStandardDev());
    }
  }
  
   public class sortByOnset implements Comparator<Layer> {
    @Override
    public int compare(Layer o1, Layer o2) {
       return o1.getOnsetTime().compareTo(o2.getOnsetTime());
    }
  }
  
  ArrayList<Layer> getSortByWeight(){ 
    
   ArrayList<Layer> newLayers = layers;
   
   Collections.sort(newLayers, new sortByWeight());
   
//   for(int i=0; i<newLayers.size(); i++){
//     println(i + " " + newLayers.get(i).id + " ---- " + newLayers.get(i).getWeight() + " " + data.getColumnName(matchLayerIDReverse(i)));
//   }
   
   return newLayers;
   
  
}


 void sort(){ 
   
   //Collections.sort(layers, new sortByWeight());
   Collections.sort(layers, new sortByStandardDev());
   Collections.sort(layers, new sortByOnset());
   
   
   for (int layerID = 1; layerID < numLayers; layerID+=2){
     Layer tempLayer = layers.get(layerID);

     for(int k = layerID; k > 0; k--) 
       layers.set(k, layers.get(k-1));
     layers.set(0, tempLayer);
   }
   
   
   while(true){
     float weightBelow = 0;
     float weightAbove = 0;
     
     
     
     for (int layerID = 0; layerID < numLayers / 2; layerID++)
       weightBelow += layers.get(layerID).getStandardDev();
       
     for (int layerID = numLayers / 2; layerID < numLayers; layerID++)
       weightAbove += layers.get(layerID).getStandardDev();
       
     if (weightAbove - layers.get(numLayers - 1).getStandardDev() > weightBelow ){
       int layerID = numLayers - 1;
       Layer tempLayer = layers.get(layerID);

       for(int k = layerID; k > 0; k--) 
         layers.set(k, layers.get(k-1));
       layers.set(0, tempLayer);
     }else if (weightAbove < weightBelow - layers.get(0).getStandardDev() ){
       int layerID = 0;
       Layer tempLayer = layers.get(layerID);

       for(int k = 0; k < numLayers - 1; k++) 
         layers.set(k, layers.get(k+1));
       layers.set(numLayers - 1, tempLayer);
     }else
       break;
   }
     
     
 }
  
}
